﻿using ICodeShare.UI.Converters.Expressions;
using ICodeShare.UI.Converters.Expressions.Nodes;
using System;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using ICodeShare.UI.Converters.Helpers;

namespace ICodeShare.UI.Converters
{
    #region IValueConverter
    /// <summary>
    /// 表达式转换器
    /// </summary>
    /// <example>
    /// 1.在xmal文件引用DateConverter类所在命名空间。
    ///   xmlns:cvts="http://schemas.extended.wpf.com/converters"
    /// 2.在xaml文件添加Resources。
    ///   <Window.Resources>
    ///   <cvts:ExpressionConverter x:Key="cvtsExpression"/>
    ///   </Window.Resources>
    /// 3.在xaml文件中指定Binding值的Converter
    ///    <TextBox Text="{Binding ShowTheText,Converter={StaticResource cvtsExpression}}"></TextBox>
    /// </example>
    [ContentProperty("Expression")]
    [ValueConversion(typeof(object), typeof(object))]
    public sealed class ExpressionConverter : IValueConverter, IMultiValueConverter
    {
        #region Members
        private static readonly ExceptionHelper exceptionHelper = new ExceptionHelper(typeof(ExpressionConverter));
        private string expression;
        private Node expressionNode;
        #endregion

        #region Construction
        /// <summary>
        /// Initializes a new instance of the ExpressionConverter class.
        /// </summary>
        public ExpressionConverter()
        {
        }

        /// <summary>
        /// Initializes a new instance of the ExpressionConverter class with the specified expression.
        /// </summary>
        /// <param name="expression">
        /// The expression (see <see cref="Expression"/>).
        /// </param>
        public ExpressionConverter(string expression)
        {
            this.Expression = expression;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the expression for this <c>MathConverter</c>.
        /// </summary>
        [ConstructorArgument("expression")]
        public string Expression
        {
            get
            {
                return this.expression;
            }

            set
            {
                this.expression = value;
                this.expressionNode = null;
            }
        }
        #endregion

        #region  Base Class Overrides 基类方法重写
        /// <summary>
        /// Attempts to convert the specified value.
        /// </summary>
        /// <param name="value">
        /// The value to convert.
        /// </param>
        /// <param name="targetType">
        /// The type of the binding target property.
        /// </param>
        /// <param name="parameter">
        /// The converter parameter to use.
        /// </param>
        /// <param name="culture">
        /// The culture to use in the converter.
        /// </param>
        /// <returns>
        /// A converted value.
        /// </returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            this.EnsureExpressionNode();
            exceptionHelper.ResolveAndThrowIf(this.expressionNode == null, "NoExpression");
            return this.expressionNode.Evaluate(NodeEvaluationContext.Create(value));
        }

        /// <summary>
        /// Attempts to convert the specified value back.
        /// </summary>
        /// <param name="value">
        /// The value to convert.
        /// </param>
        /// <param name="targetType">
        /// The type of the binding target property.
        /// </param>
        /// <param name="parameter">
        /// The converter parameter to use.
        /// </param>
        /// <param name="culture">
        /// The culture to use in the converter.
        /// </param>
        /// <returns>
        /// A converted value.
        /// </returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DependencyProperty.UnsetValue;
        }

        /// <summary>
        /// Attempts to convert the specified values.
        /// </summary>
        /// <param name="values">
        /// The values to convert.
        /// </param>
        /// <param name="targetType">
        /// The type of the binding target property.
        /// </param>
        /// <param name="parameter">
        /// The converter parameter to use.
        /// </param>
        /// <param name="culture">
        /// The culture to use in the converter.
        /// </param>
        /// <returns>
        /// A converted value.
        /// </returns>
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            this.EnsureExpressionNode();
            exceptionHelper.ResolveAndThrowIf(this.expressionNode == null, "NoExpression");
            return this.expressionNode.Evaluate(NodeEvaluationContext.Create(values));
        }

        /// <summary>
        /// Attempts to convert back the specified values.
        /// </summary>
        /// <param name="value">
        /// The value to convert.
        /// </param>
        /// <param name="targetTypes">
        /// The types of the binding target properties.
        /// </param>
        /// <param name="parameter">
        /// The converter parameter to use.
        /// </param>
        /// <param name="culture">
        /// The culture to use in the converter.
        /// </param>
        /// <returns>
        /// Converted values.
        /// </returns>
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            return null;
        }
        #endregion

        #region Methods
        // turn the expression into an AST each time it changes (not each time our Convert methods are called)
        private void EnsureExpressionNode()
        {
            if (this.expressionNode != null || string.IsNullOrEmpty(this.expression))
            {
                return;
            }

            using (var stringReader = new StringReader(this.expression))
            using (var tokenizer = new Tokenizer(stringReader))
            using (var parser = new Parser(tokenizer))
            {
                this.expressionNode = parser.ParseExpression();
            }
        }
        #endregion
    }
    #endregion
}
